package com.augmentum.login.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.augmentum.login.common.ConfigAction;
import com.augmentum.login.common.ModelView;
import com.augmentum.login.common.ResultConfig;
import com.augmentum.login.common.ViewParameter;
import com.augmentum.login.util.Prop;
import com.augmentum.login.util.SpringUtil;
import com.augmentum.login.util.StringUtil;

public class DispatcherServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private Map<String, ConfigAction> config = new HashMap<String, ConfigAction>();
    private String suffix = ".action"; // set default suffix

    @Override
    public void init(ServletConfig serverConfig) throws ServletException {
        super.init(serverConfig);
        if (!StringUtil.isEmpty(serverConfig.getInitParameter("suffix"))) {
            suffix = serverConfig.getInitParameter("suffix");
        }
        InputStream in = null;
        try {
            in = Prop.class.getClassLoader().getResourceAsStream("action.xml");
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(in);
            Element elem = doc.getDocumentElement();
            NodeList actionNodes = elem.getElementsByTagName("action");
            if (actionNodes == null) {
                return;
            }

            int size = actionNodes.getLength();
            for (int i = 0; i < size; i++) {
                Element element = (Element) actionNodes.item(i);
                String name = element.getAttribute("name");
                String cls = element.getAttribute("class");
                String methodName = element.getAttribute("method");
                ConfigAction action = new ConfigAction();
                action.setClsName(cls);
                action.setMethod(methodName);

                String methodTypes = element.getAttribute("methodType");
                if (StringUtil.isEmpty(methodTypes)) {
                    methodTypes = "GET";
                }
                String[] methodArr = StringUtil.split(methodTypes, ",");

                action.setMethodTypes(methodArr);

                for (String methodItem : methodArr) {
                    config.put(name + suffix + "#" + methodItem.toUpperCase(), action);
                }

                if (element.hasChildNodes()) {
                    NodeList resultElems = element.getElementsByTagName("result");
                    if (resultElems == null) {
                        return;
                    }
                    for (int j = 0; j < resultElems.getLength(); j++) {
                        ResultConfig rc = new ResultConfig();
                        Element e = (Element) resultElems.item(j);
                        String rname = e.getAttribute("name");
                        String url = e.getAttribute("view");
                        String redirect = e.getAttribute("redirect");
                        if (StringUtil.isEmpty(redirect)) {
                            redirect = "false";
                        }
                        rc.setName(rname);
                        ;
                        rc.setRedirect(Boolean.parseBoolean(redirect));
                        rc.setView(url);
                        String viewParam = e.getAttribute("viewParam");
                        if (!StringUtil.isEmpty(viewParam)) {
                            String[] params = viewParam.split(",");
                            for (String param : params) {
                                String[] values = StringUtil.split(param, ":");
                                ViewParameter vp = new ViewParameter();
                                vp.setName(values[0].trim());
                                String from = "attribute";
                                if (values.length == 2) {
                                    from = values[1].trim();
                                }
                                vp.setFrom(from);
                                rc.addViewParameter(vp);
                            }
                        }
                        action.addResultConfig(rname, rc);
                    }
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public DispatcherServlet() {
        super();
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException,
            IOException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String uri = request.getRequestURI();
        String requestedUri = uri.substring(request.getContextPath().length() + 1);
        if (requestedUri == null || requestedUri.equals("")) {
            requestedUri = "login" + this.suffix;
        }
        String httpMethod = request.getMethod();
        ConfigAction actionConfig = config.get(requestedUri + "#" + httpMethod.toUpperCase());
        HttpSession session = request.getSession();

        if (actionConfig != null) {
            String clsName = actionConfig.getClsName();
            try {
                Class<?> cls = Class.forName(clsName);
                String cName = clsName.substring(clsName.lastIndexOf('.') + 1);
                String cNameFirst = cName.substring(0, 1).toLowerCase();
                String id = cNameFirst + cName.substring(1);

                //Object controller = BeanFactory.getInstance().getBean(id);
                Object controller = SpringUtil.getBean(id);

                String methodName = actionConfig.getMethod();
                Method method = cls.getMethod(methodName, new Class[] { Map.class, Map.class });
                Map<String, Object> sessionMap = new HashMap<String, Object>();
                Enumeration<String> toSessionKeys = session.getAttributeNames();
                while (toSessionKeys.hasMoreElements()) {
                    String toKey = toSessionKeys.nextElement();
                    sessionMap.put(toKey, session.getAttribute(toKey));
                }
                Map<String, String> parameterMap = new HashMap<String, String>();
                Enumeration<String> toRequestKeys = request.getParameterNames();
                while (toRequestKeys.hasMoreElements()) {
                    String toKey = toRequestKeys.nextElement();
                    parameterMap.put(toKey, request.getParameter(toKey));
                }
                ModelView modelView = (ModelView) method.invoke(controller, new Object[] { parameterMap, sessionMap });
                Map<String, Object> fromControllerSession = modelView.getSession();
                Set<String> keys = fromControllerSession.keySet();
                for (String key : keys) {
                    session.setAttribute(key, fromControllerSession.get(key));
                }
                Map<String, Object> fromControllerRequest = modelView.getParameter();
                Set<String> keyRequests = fromControllerRequest.keySet();
                for (String key : keyRequests) {
                    request.setAttribute(key, fromControllerRequest.get(key));
                }
                String view = modelView.getView();
                ResultConfig resultConfig = actionConfig.getResult(view);
                if (resultConfig == null) {
                    if (modelView.isRedirect()) {
                        response.sendRedirect(request.getContextPath() + "/" + view);
                    } else {
                        request.getRequestDispatcher(view).forward(request, response);
                    }
                } else {
                    String resultView = request.getContextPath() + "/" + resultConfig.getView();
                    if (resultConfig.isRedirect()) {
                        List<ViewParameter> viewParameter = resultConfig.getViewParamlist();
                        if (viewParameter == null || viewParameter.isEmpty()) {
                            response.sendRedirect(resultView);
                        } else {
                            StringBuilder sb = new StringBuilder();
                            for (ViewParameter viewParameterConfig : viewParameter) {
                                String name = viewParameterConfig.getName();
                                String from = viewParameterConfig.getFrom();
                                String value = "";
                                if ("attribute".equals(from)) {
                                    value = (String) request.getAttribute(name);
                                } else if ("parameter".equals(from)) {
                                    value = request.getParameter(name);
                                } else if ("session".equals(from)) {
                                    value = (String) request.getSession().getAttribute(name);
                                } else {
                                    value = (String) request.getAttribute(name);
                                }
                                if (!StringUtil.isEmpty(value)) {
                                    sb.append(name + "=" + value + "&");
                                }
                            }
                            if (resultView.indexOf("?") > 0) {
                                resultView = resultView + "&" + sb.toString();
                            } else {
                                resultView = resultView + "?" + sb.toString();
                            }
                            response.sendRedirect(resultView);
                        }
                    } else {
                        request.getRequestDispatcher(resultConfig.getView()).forward(request, response);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                response.sendError(500);
            }
        } else {
            response.sendError(404);
        }
    }
}
